package com.lagou.niancheng.core.parse;

import com.lagou.niancheng.core.config.BoundSql;
import com.lagou.niancheng.core.config.ClassMetaData;
import com.lagou.niancheng.core.config.Configuration;
import com.lagou.niancheng.core.config.MappedStatement;
import com.lagou.niancheng.core.config.MethodMetaData;
import com.lagou.niancheng.core.config.Param;
import com.lagou.niancheng.core.config.ParamObject;
import com.lagou.niancheng.core.config.ResultTypeMetaData;
import com.lagou.niancheng.core.config.StatementType;
import com.lagou.niancheng.core.exceptions.LagouMybatisException;
import com.lagou.niancheng.core.utils.GenericTokenParser;
import com.lagou.niancheng.core.utils.ParameterMapping;
import com.lagou.niancheng.core.utils.ParameterMappingTokenHandler;
import com.lagou.niancheng.core.utils.ReflectionUtil;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class XMLMapperBuilder {

    private Configuration configuration;

    public XMLMapperBuilder(Configuration configuration) {
        this.configuration = configuration;
    }

    public void parse(InputStream mapperInputStream) throws DocumentException {
        // 构建document对象
        Document document = new SAXReader().read(mapperInputStream);
        // 获取root节点
        Element rootElement = document.getRootElement();
        // 获取mapper节点上的namespace属性
        String namespace = rootElement.attributeValue("namespace");
        // 解析insert标签语句
        parseStatements(rootElement, namespace, StatementType.INSERT);
        // 解析delete标签语句
        parseStatements(rootElement, namespace, StatementType.DELETE);
        // 解析select标签语句
        parseStatements(rootElement, namespace, StatementType.SELECT);
        // 解析update标签语句
        parseStatements(rootElement, namespace, StatementType.UPDATE);
    }


    @SuppressWarnings("unchecked")
    private void parseStatements(Element root, String namespace, StatementType statementType) {
        List<Element> list = root.selectNodes(statementType.getTagExpression());
        if (list == null) {
            return;
        }
        Class aClass = ReflectionUtil.tryGetClass(namespace);
        Map<String, MethodMetaData> methodMap = Collections.emptyMap();
        if (aClass != null) {
            ClassMetaData classMetaData = getClassMetaData(aClass);
            methodMap = classMetaData.getMethodMap();
        }
        for (Element element : list) {
            String id = element.attributeValue("id");
            String statementKey = namespace + "." + id;
            Map<String, MappedStatement> mappedStatementMap = configuration.getMappedStatementMap();
            // ========== 1.校验,如果发现语句重复及时报错
            if (mappedStatementMap.containsKey(statementKey)) {
                throw new LagouMybatisException(MessageFormat.format("statement:{0} is already exist",
                        statementKey));
            }
            MappedStatement mappedStatement = new MappedStatement();
            // =========== 2.设置sql
            String sqlText = element.getTextTrim();
            mappedStatement.setSql(sqlText);
            // 解析sql语句
            BoundSql boundSql = getBoundSql(sqlText);
            mappedStatement.setBoundSql(boundSql);
            mappedStatement.setStatementType(statementType);
            mappedStatement.setId(id);
            MethodMetaData methodMetaData = methodMap.get(id);
            // =========== 3.设置方法元数据
            if (methodMetaData != null) {
                mappedStatement.setMethodMetaData(methodMetaData);
            }
            // =========== 4.设置结果元数据
            String resultTypeName = element.attributeValue("resultType");
            if (resultTypeName != null) {
                Class resultType = ReflectionUtil.getClass(resultTypeName);
                ResultTypeMetaData resultTypeMetaData = new ResultTypeMetaData(resultType);
                resultTypeMetaData.setResultType(resultType);
                mappedStatement.setResultTypeMetaData(resultTypeMetaData);
            }
            // =========== 5.设置参数类型
            String parameterTypeName = element.attributeValue("parameterType");
            if (parameterTypeName != null) {
                mappedStatement.setParameterType(ReflectionUtil.getClass(parameterTypeName));
            }

            configuration.getMappedStatementMap().put(statementKey, mappedStatement);
        }
    }


    private ClassMetaData getClassMetaData(Class aClass) {
        ClassMetaData classMetaData = new ClassMetaData();
        classMetaData.setMapperClass(aClass);
        Method[] declaredMethods = aClass.getDeclaredMethods();
        Map<String, MethodMetaData> methodMap = new HashMap<>(declaredMethods.length);
        for (Method declaredMethod : declaredMethods) {
            MethodMetaData methodMetaData = parseMethod(declaredMethod);
            methodMap.put(declaredMethod.getName(), methodMetaData);
        }
        classMetaData.setMethodMap(methodMap);
        return classMetaData;
    }

    private MethodMetaData parseMethod(Method declaredMethod) {
        MethodMetaData methodMetaData = new MethodMetaData();
        methodMetaData.setMethod(declaredMethod);
        methodMetaData.setGenericReturnType(declaredMethod.getGenericReturnType());

        Parameter[] parameters = declaredMethod.getParameters();
        if (parameters == null) {
            return methodMetaData;
        }
        Map<String, Integer> paramIndexMap = new HashMap<>(parameters.length);
        methodMetaData.setAComplexObjectParam(false);
        for (int i = 0; i < parameters.length; i++) {
            // 解析参数是简单参数
            Parameter parameter = parameters[i];
            Param param = parameter.getAnnotation(Param.class);
            if (param != null) {
                String name = param.value();
                paramIndexMap.put(name, i);
                continue;
            }
            // 解析参数是复杂参数
            ParamObject paramObject = parameter.getAnnotation(ParamObject.class);
            if (paramObject != null) {
                methodMetaData.setAComplexObjectParam(true);
                methodMetaData.setComplexObjectParamType(parameter.getType());
                break;
            }
            throw new LagouMybatisException("invalid param without any annotation(@Param, @ParamObject)");
        }
        methodMetaData.setSimpleParamIndexMap(paramIndexMap);
        return methodMetaData;
    }


    /**
     * 完成对#{}的解析工作：1.将#{}使用？进行代替，2.解析出#{}里面的值进行存储
     *
     * @param sql
     * @return
     */
    private BoundSql getBoundSql(String sql) {
        //标记处理类：配置标记解析器来完成对占位符的解析处理工作
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
        //解析出来的sql
        String parseSql = genericTokenParser.parse(sql);
        //#{}里面解析出来的参数名称
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();

        return new BoundSql(parseSql, parameterMappings);
    }

}
